#pragma once

#if 0
#include "Common.h"
#include "ByteBuffer.h"
#endif

enum TypeID
{
    TYPEID_OBJECT = 0,
    TYPEID_ITEM = 1,
    TYPEID_CONTAINER = 2,
    TYPEID_UNIT = 3,
    TYPEID_PLAYER = 4,
    TYPEID_GAMEOBJECT = 5,
    TYPEID_DYNAMICOBJECT = 6,
    TYPEID_CORPSE = 7
};

#define MAX_TYPE_ID        8

enum TypeMask
{
    TYPEMASK_OBJECT = 0x0001,
    TYPEMASK_ITEM = 0x0002,
    TYPEMASK_CONTAINER = 0x0004,
    TYPEMASK_UNIT = 0x0008,                       // players also have it
    TYPEMASK_PLAYER = 0x0010,
    TYPEMASK_GAMEOBJECT = 0x0020,
    TYPEMASK_DYNAMICOBJECT = 0x0040,
    TYPEMASK_CORPSE = 0x0080,

    // used combinations in Player::GetObjectByTypeMask (TYPEMASK_UNIT case ignore players in call)
    TYPEMASK_CREATURE_OR_GAMEOBJECT = TYPEMASK_UNIT | TYPEMASK_GAMEOBJECT,
    TYPEMASK_CREATURE_GAMEOBJECT_OR_ITEM = TYPEMASK_UNIT | TYPEMASK_GAMEOBJECT | TYPEMASK_ITEM,
    TYPEMASK_CREATURE_GAMEOBJECT_PLAYER_OR_ITEM = TYPEMASK_UNIT | TYPEMASK_GAMEOBJECT | TYPEMASK_ITEM | TYPEMASK_PLAYER,

    TYPEMASK_WORLDOBJECT = TYPEMASK_UNIT | TYPEMASK_PLAYER | TYPEMASK_GAMEOBJECT | TYPEMASK_DYNAMICOBJECT | TYPEMASK_CORPSE,
};

enum HighGuid
{
    HIGHGUID_ITEM = 0x4000,                       // blizz 4000
    HIGHGUID_CONTAINER = 0x4000,                       // blizz 4000
    HIGHGUID_PLAYER = 0x0000,                       // blizz 0000
    HIGHGUID_GAMEOBJECT = 0xF110,                       // blizz F110
    HIGHGUID_TRANSPORT = 0xF120,                       // blizz F120 (for GAMEOBJECT_TYPE_TRANSPORT)
    HIGHGUID_UNIT = 0xF130,                       // blizz F130
    HIGHGUID_PET = 0xF140,                       // blizz F140
    HIGHGUID_DYNAMICOBJECT = 0xF100,                       // blizz F100
    HIGHGUID_CORPSE = 0xF101,                       // blizz F100
    HIGHGUID_MO_TRANSPORT = 0x1FC0,                       // blizz 1FC0 (for GAMEOBJECT_TYPE_MO_TRANSPORT)
};

class ObjectGuid;
class PackedGuid;

struct PackedGuidReader
{
    explicit PackedGuidReader(ObjectGuid& guid) : m_guidPtr(&guid) {}
    ObjectGuid* m_guidPtr;
};

class ObjectGuid
{
public:                                                 // constructors
    ObjectGuid() : m_guid(0) {}
    ObjectGuid(unsigned long long const& guid) : m_guid(guid) {}    // temporary allowed implicit cast, really bad in connection with operator unsigned long long()
    ObjectGuid(HighGuid hi, unsigned int entry, unsigned int counter) : m_guid(counter ? unsigned long long(counter) | (unsigned long long(entry) << 24) | (unsigned long long(hi) << 48) : 0) {}
    ObjectGuid(HighGuid hi, unsigned int counter) : m_guid(counter ? unsigned long long(counter) | (unsigned long long(hi) << 48) : 0) {}

    operator unsigned long long() const { return m_guid; }
private:
    ObjectGuid(unsigned int const&);                          // no implementation, used for catch wrong type assign
    ObjectGuid(HighGuid, unsigned int, unsigned long long counter);       // no implementation, used for catch wrong type assign
    ObjectGuid(HighGuid, unsigned long long counter);               // no implementation, used for catch wrong type assign

public:                                                 // modifiers
    PackedGuidReader ReadAsPacked() { return PackedGuidReader(*this); }

    void Set(unsigned long long const& guid) { m_guid = guid; }
    void Clear() { m_guid = 0; }

    PackedGuid WriteAsPacked() const;
public:                                                 // accessors
    unsigned long long const& GetRawValue() const { return m_guid; }
    HighGuid GetHigh() const { return HighGuid((m_guid >> 48) & 0x0000FFFF); }
#if 1
    unsigned int   GetEntry() const { return 0; }
#else
    unsigned int   GetEntry() const { return HasEntry() ? unsigned int((m_guid >> 24) & UI64LIT(0x0000000000FFFFFF)) : 0; }
#endif
    unsigned int   GetCounter()  const
    {
#if 1
        return 0;
#else
        return HasEntry()
            ? unsigned int(m_guid & UI64LIT(0x0000000000FFFFFF))
            : unsigned int(m_guid & UI64LIT(0x00000000FFFFFFFF));
#endif
    }

    static unsigned int GetMaxCounter(HighGuid high)
    {
        return HasEntry(high)
            ? unsigned int(0x00FFFFFF)
            : unsigned int(0xFFFFFFFF);
    }

    unsigned int GetMaxCounter() const { return GetMaxCounter(GetHigh()); }

    bool IsEmpty()             const { return m_guid == 0; }
    bool IsCreature()          const { return GetHigh() == HIGHGUID_UNIT; }
    bool IsPet()               const { return GetHigh() == HIGHGUID_PET; }
    bool IsCreatureOrPet()     const { return IsCreature() || IsPet(); }
    bool IsAnyTypeCreature()   const { return IsCreature() || IsPet(); } // wrapper to master branch
    bool IsPlayer()            const { return !IsEmpty() && GetHigh() == HIGHGUID_PLAYER; }
    bool IsUnit()              const { return IsAnyTypeCreature() || IsPlayer(); }
    bool IsItem()              const { return GetHigh() == HIGHGUID_ITEM; }
    bool IsGameObject()        const { return GetHigh() == HIGHGUID_GAMEOBJECT; }
    bool IsDynamicObject()     const { return GetHigh() == HIGHGUID_DYNAMICOBJECT; }
    bool IsCorpse()            const { return GetHigh() == HIGHGUID_CORPSE; }
    bool IsTransport()         const { return GetHigh() == HIGHGUID_TRANSPORT; }
    bool IsMOTransport()       const { return GetHigh() == HIGHGUID_MO_TRANSPORT; }

    static TypeID GetTypeId(HighGuid high)
    {
        switch (high)
        {
        case HIGHGUID_ITEM:         return TYPEID_ITEM;
            // case HIGHGUID_CONTAINER:    return TYPEID_CONTAINER; HIGHGUID_CONTAINER==HIGHGUID_ITEM currently
        case HIGHGUID_UNIT:         return TYPEID_UNIT;
        case HIGHGUID_PET:          return TYPEID_UNIT;
        case HIGHGUID_PLAYER:       return TYPEID_PLAYER;
        case HIGHGUID_GAMEOBJECT:   return TYPEID_GAMEOBJECT;
        case HIGHGUID_DYNAMICOBJECT: return TYPEID_DYNAMICOBJECT;
        case HIGHGUID_CORPSE:       return TYPEID_CORPSE;
        case HIGHGUID_MO_TRANSPORT: return TYPEID_GAMEOBJECT;
            // unknown
        default:                    return TYPEID_OBJECT;
        }
    }

    TypeID GetTypeId() const { return GetTypeId(GetHigh()); }

    bool operator!() const { return IsEmpty(); }
    bool operator== (ObjectGuid const& guid) const { return GetRawValue() == guid.GetRawValue(); }
    bool operator!= (ObjectGuid const& guid) const { return GetRawValue() != guid.GetRawValue(); }
    bool operator< (ObjectGuid const& guid) const { return GetRawValue() < guid.GetRawValue(); }

public:                                                 // accessors - for debug
    static char const* GetTypeName(HighGuid high);
    char const* GetTypeName() const { return !IsEmpty() ? GetTypeName(GetHigh()) : "None"; }
    std::string GetString() const;

private:                                                // internal functions
    static bool HasEntry(HighGuid high)
    {
        switch (high)
        {
        case HIGHGUID_ITEM:
        case HIGHGUID_PLAYER:
        case HIGHGUID_DYNAMICOBJECT:
        case HIGHGUID_CORPSE:
        case HIGHGUID_MO_TRANSPORT:
            return false;
        case HIGHGUID_GAMEOBJECT:
        case HIGHGUID_TRANSPORT:
        case HIGHGUID_UNIT:
        case HIGHGUID_PET:
        default:
            return true;
        }
    }

    bool HasEntry() const { return HasEntry(GetHigh()); }

private:                                                // fields
    unsigned long long m_guid;
};

namespace std {
    template<>
    struct hash<ObjectGuid> {
        std::size_t operator()(const ObjectGuid& k) const {
            return (unsigned long long)k;
        }
    };
}

// Some Shared defines
typedef std::set<ObjectGuid> GuidSet;
typedef std::list<ObjectGuid> GuidList;
typedef std::vector<ObjectGuid> GuidVector;

// minimum buffer size for packed guid is 9 bytes
#define PACKED_GUID_MIN_BUFFER_SIZE 9

#if 0
class PackedGuid
{
#if 0
    friend ByteBuffer& operator<< (ByteBuffer& buf, PackedGuid const& guid);
#endif
public:                                                 // constructors
    explicit PackedGuid() : m_packedGuid(PACKED_GUID_MIN_BUFFER_SIZE) { m_packedGuid.appendPackGUID(0); }
    explicit PackedGuid(unsigned long long const& guid) : m_packedGuid(PACKED_GUID_MIN_BUFFER_SIZE) { m_packedGuid.appendPackGUID(guid); }
    explicit PackedGuid(ObjectGuid const& guid) : m_packedGuid(PACKED_GUID_MIN_BUFFER_SIZE) { m_packedGuid.appendPackGUID(guid.GetRawValue()); }

public:                                                 // modifiers
    void Set(unsigned long long const& guid) { m_packedGuid.wpos(0); m_packedGuid.appendPackGUID(guid); }
    void Set(ObjectGuid const& guid) { m_packedGuid.wpos(0); m_packedGuid.appendPackGUID(guid.GetRawValue()); }

public:                                                 // accessors
    size_t size() const { return m_packedGuid.size(); }

private:                                                // fields
    ByteBuffer m_packedGuid;
};
#endif

template<HighGuid high>
class ObjectGuidGenerator
{
public:                                                 // constructors
    explicit ObjectGuidGenerator(unsigned int start = 1) : m_nextGuid(start) {}

public:                                                 // modifiers
    void Set(unsigned int val) { m_nextGuid = val; }
    unsigned int Generate();

public:                                                 // accessors
    unsigned int GetNextAfterMaxUsed() const { return m_nextGuid; }

private:                                                // fields
    unsigned int m_nextGuid;
};

#if 0
ByteBuffer& operator<< (ByteBuffer& buf, ObjectGuid const& guid);
ByteBuffer& operator>> (ByteBuffer& buf, ObjectGuid& guid);

ByteBuffer& operator<< (ByteBuffer& buf, PackedGuid const& guid);
ByteBuffer& operator>> (ByteBuffer& buf, PackedGuidReader const& guid);

inline PackedGuid ObjectGuid::WriteAsPacked() const { return PackedGuid(*this); }

HASH_NAMESPACE_START

template<>
struct hash<ObjectGuid>
{
public:

    size_t operator()(ObjectGuid const& key) const
    {
        return hash<unsigned long long>()(key.GetRawValue());
    }
};
#endif